package lesson4.example1;

import java.io.File;
import java.io.IOException;

import org.apache.commons.io.FileUtils;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

import com.classloading.utils.JavaCompilerUtil;

public class TestASM {

	public static String path = JavaCompilerUtil.path +"/bytecode-examples/bytecode-example-4-01/src/main/java/";
	public static String destpath = JavaCompilerUtil.path +"/bytecode-examples/bytecode-example-4-01/output";
	
	
	public static void main(String args[]) throws IOException {
		JavaCompilerUtil.compile(path+"/lesson4/example1/TestDemo.java", destpath);
		testAddMethodByASM();
		JavaCompilerUtil.decompile(destpath+"/lesson4/example1/TestDemo.class");
//		JavaCompilerUtil.run(new String[]{
//				"java","-cp",destpath,"lesson4.example1.TestDemo"
//		});
	}

	// ClassReader读取字节码，然后交给ClassVisitor类处理，处理完成后由ClassWriter写字节码并将旧的字节码替换掉
	public static void testAddMethodByASM() throws IOException {
		byte[] bytes = FileUtils.readFileToByteArray(new File(destpath + "/lesson4/example1/TestDemo.class"));

		// ClassReader可对字节码进行读取和分析，当有对应的读取事件发生时，会触发相应的ClassVisitor、MethodVisitor等做相应的处理
		ClassReader classReader = new ClassReader(bytes);

		// ClassWriter实现了抽象类ClassVisitor，在ClassVisitor的visit方法中可对字节码进行修改
		ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);

		// ClassVisitor是一个抽象类，所以不能直接实例化，使用时需要继承此类。将cv传递参ClassReader的accept()方法，
		// 这样ClassReader在解析Class文件过程中，遇到不同的节点就会调用ClassVisitor不同的visit方法了
		ClassVisitor classVisitor = new ClassVisitor(Opcodes.ASM7, classWriter) {
			public MethodVisitor visitMethod(int access, String name, String desc, String signature,
					String[] exceptions) {
				if ("foo".equals(name)) { // 删除原有的foo方法
					return null;
				}
				return super.cv.visitMethod(access, name, desc, signature, exceptions);
			}
			
            // visitEnd()方法会在最后被调用
			public void visitEnd() { // 新增一个foo方法
				MethodVisitor mv = cv.visitMethod(Opcodes.ACC_PUBLIC, "foo", "(I)I", null, null);
				mv.visitCode();
				mv.visitVarInsn(Opcodes.ILOAD, 1);
				mv.visitIntInsn(Opcodes.BIPUSH, 102);
				mv.visitInsn(Opcodes.IADD);
				mv.visitInsn(Opcodes.IRETURN);
				// 触发对本地变量表和操作数栈大小的计算，当然也可以直接指定为
				// mv.visitMaxs(2,2)
				mv.visitMaxs(0, 0); 
				mv.visitEnd();
			}
		};

		classReader.accept(classVisitor, 0);

		byte[] bytesModified = classWriter.toByteArray();
		FileUtils.writeByteArrayToFile(new File(destpath + "/lesson4/example1/TestDemo.class"), bytesModified);
	}

}
